#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
行业股票技术面分析工具 - 工具函数模块

作者: Claude
日期: 2024-05-14
"""

import os
import time
import logging
import functools
import datetime
import pandas as pd
import numpy as np
from functools import wraps
import traceback
import config

def setup_logger(name, log_file, level=logging.INFO):
    """设置日志记录器
    
    Args:
        name: 日志记录器名称
        log_file: 日志文件路径
        level: 日志级别
        
    Returns:
        logger: 日志记录器
    """
    # 创建日志目录
    log_dir = os.path.dirname(log_file)
    if not os.path.exists(log_dir):
        os.makedirs(log_dir)
    
    # 设置日志记录器
    formatter = logging.Formatter(config.LOG_CONFIG['format'])
    
    handler = logging.FileHandler(log_file, encoding='utf-8')
    handler.setFormatter(formatter)
    
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)
    
    logger = logging.getLogger(name)
    logger.setLevel(level)
    
    # 防止重复添加处理器
    if not logger.handlers:
        logger.addHandler(handler)
        logger.addHandler(console_handler)
    
    return logger

def retry(max_tries=3, delay=1):
    """重试装饰器
    
    Args:
        max_tries: 最大重试次数
        delay: 重试延迟(秒)
        
    Returns:
        decorator: 装饰器函数
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            tries = 0
            last_exception = None
            while tries < max_tries:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    tries += 1
                    last_exception = e
                    logger = logging.getLogger(func.__module__)
                    logger.warning(f"函数 {func.__name__} 执行失败 (尝试 {tries}/{max_tries}): {e}")
                    if tries < max_tries:
                        logger.info(f"等待 {delay} 秒后重试...")
                        time.sleep(delay)
                    else:
                        logger.error(f"函数 {func.__name__} 已达到最大重试次数，抛出异常: {e}")
                        logger.error(f"异常详情: {traceback.format_exc()}")
            raise last_exception
        return wrapper
    return decorator

def save_to_csv(df, file_path):
    """保存DataFrame到CSV文件
    
    Args:
        df: 数据框
        file_path: 文件路径
    """
    if df is None or df.empty:
        logger = logging.getLogger('utils')
        logger.warning(f"尝试保存空的DataFrame到 {file_path}")
        return
        
    directory = os.path.dirname(file_path)
    if not os.path.exists(directory):
        os.makedirs(directory)
    
    # 确保没有非法字符
    try:
        df.to_csv(file_path, encoding='utf-8-sig', index=False)
    except Exception as e:
        logger = logging.getLogger('utils')
        logger.error(f"保存CSV文件 {file_path} 失败: {e}")
        
        # 尝试修复日期列
        try:
            if '日期' in df.columns:
                df['日期'] = df['日期'].astype(str)
            df.to_csv(file_path, encoding='utf-8-sig', index=False)
            logger.info(f"修复日期列后成功保存CSV文件 {file_path}")
        except Exception as e2:
            logger.error(f"再次尝试保存CSV失败: {e2}")

def get_date_range(days=120):
    """获取日期范围
    
    Args:
        days: 历史数据天数
        
    Returns:
        start_date: 开始日期 (str, 格式: YYYY-MM-DD)
        end_date: 结束日期 (str, 格式: YYYY-MM-DD)
    """
    end_date = datetime.datetime.now()
    
    # 确保结束日期是交易日（周一至周五）
    while end_date.weekday() > 4:  # 5和6代表周六和周日
        end_date -= datetime.timedelta(days=1)
    
    start_date = end_date - datetime.timedelta(days=days)
    
    return start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d')

def format_date(date_obj):
    """格式化日期对象为标准字符串
    
    Args:
        date_obj: 日期对象
        
    Returns:
        str: 格式化的日期字符串（YYYY-MM-DD）
    """
    if pd.isna(date_obj):
        return None
    
    try:
        if isinstance(date_obj, (datetime.datetime, datetime.date)):
            return date_obj.strftime('%Y-%m-%d')
        elif isinstance(date_obj, str):
            # 尝试转换为datetime再格式化
            return pd.to_datetime(date_obj).strftime('%Y-%m-%d')
        elif isinstance(date_obj, (int, float)):
            # 假设是时间戳（毫秒）
            return pd.to_datetime(date_obj, unit='ms').strftime('%Y-%m-%d')
        else:
            return str(date_obj)
    except Exception as e:
        logger = logging.getLogger('utils')
        logger.warning(f"日期格式化失败: {e}, 值: {date_obj}, 类型: {type(date_obj)}")
        return str(date_obj)

def ensure_numeric(df, columns):
    """确保指定列为数值类型
    
    Args:
        df: 数据框
        columns: 需要确保为数值类型的列名列表
        
    Returns:
        DataFrame: 处理后的数据框
    """
    if df is None or df.empty:
        return df
        
    for col in columns:
        if col in df.columns:
            try:
                df[col] = pd.to_numeric(df[col], errors='coerce')
            except Exception as e:
                logger = logging.getLogger('utils')
                logger.warning(f"转换列 {col} 为数值类型失败: {e}")
    
    return df

def classify_score(score):
    """根据得分进行分类
    
    Args:
        score: 技术面得分
        
    Returns:
        recommendation: 投资建议
    """
    if pd.isna(score):
        return "数据不足"
        
    if score >= config.RATING_STANDARDS['strong_buy']:
        return "强烈推荐"
    elif score >= config.RATING_STANDARDS['buy']:
        return "推荐"
    elif score >= config.RATING_STANDARDS['hold']:
        return "观望"
    else:
        return "回避"

def get_score_color(score):
    """根据得分获取颜色
    
    Args:
        score: 技术面得分
        
    Returns:
        color: 颜色代码
    """
    if pd.isna(score):
        return "#999999"  # 灰色
        
    if score >= config.RATING_STANDARDS['strong_buy']:
        return "#00CC00"  # 绿色
    elif score >= config.RATING_STANDARDS['buy']:
        return "#00FF99"  # 浅绿色
    elif score >= config.RATING_STANDARDS['hold']:
        return "#FFCC00"  # 黄色
    else:
        return "#FF3333"  # 红色

def format_pct(value):
    """格式化百分比
    
    Args:
        value: 值
        
    Returns:
        formatted: 格式化后的百分比字符串
    """
    if pd.isna(value):
        return "--"
    return f"{value:.2f}%" 